上一篇:【Day 23】增加功能:利用FormField新增資料
本篇中將會把昨天的內容實際應用在Schedrag程式中。那麼就直接來看程式,再來做說明吧!
在presentation/pages/todo.dart
中,設定按下右下角的懸浮按鈕會開啟一個暫時性的widget(利用Navigator.push
),並且新頁面中的物件為EntryForm
,並輸入現在開啟的資料庫作為參數,以便後續做新增資料的動作。
floatingActionButton: FloatingActionButton(
// add new timeblock
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EntryForm(db: timeblocksdb),
));
},
child: const Icon(Icons.add),
),
下列為程式的主要框架,因內容較長,故在此做部份函式的省略,而省略部份於下方將會詳細說明。
import 'package:flutter/material.dart';
import 'package:schedrag/data/models/child_blocks.dart';
enum Tags { name, category, notes }
class EntryForm extends StatefulWidget {
final TimeBlocksDb? db;
const EntryForm({super.key, required this.db});
@override
State<EntryForm> createState() => _EntryFormState(db);
}
class _EntryFormState extends State<EntryForm> {
final TimeBlocksDb? db;
final _formKey = GlobalKey<FormState>();
List<TextEditingController> controllers = [...];
_EntryFormState(this.db);
@override
void dispose() {...}
@override
Widget build(context) {
return Scaffold(
appBar: AppBar(
title: const Text("Add New Entry"),
),
body: Form(...),
floatingActionButton: FloatingActionButton(...),
);
}
}
此部份參考了官方的Cookbook - Retrieve the value of a text field
在這裡使用的TextEditingController
可用於取得TextFormField
或TextField
類別的輸入資訊。至於該如何讓各個FormField知道該使用哪一個controller,則是在TextFormField
的controller property中設定。
List<TextEditingController> controllers = [
TextEditingController(),
TextEditingController(),
TextEditingController()
];
// set controller
TextFormField(
...
controller: controllers[0],
...
)
這部份是用於當EntryForm物件結束使用並移除資料時,剛剛建立的controller也能一起順利移除暫存資料。
@override
void dispose() {
for (var controller in controllers) {
controller.dispose();
}
super.dispose();
}
此部份參考了官方的Cookbook - Build a form with validation。
筆者希望使用者在新增資料時,name一欄不是空白,因此Name的TextFormField在validator
property中設定了欄位輸入時的規則,而autovalidateMode
則可以決定該在哪些時候去檢查輸入內容。關於_formKey
該如何使用,將在下方談論新增資料按鈕時介紹。
body: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(
icon: Icon(Icons.abc_rounded),
labelText: "Name *",
),
validator: (value) {
return (value == null || value.isEmpty)
? "new block must have a name"
: null;
},
controller: controllers[Tags.name.index],
autovalidateMode: AutovalidateMode.onUnfocus,
),
TextFormField(
decoration: const InputDecoration(
icon: Icon(Icons.category_rounded),
labelText: "Category",
),
controller: controllers[Tags.category.index],
),
TextFormField(
decoration: const InputDecoration(
icon: Icon(Icons.sticky_note_2_rounded),
labelText: "Notes",
),
controller: controllers[Tags.notes.index],
),
],
),
),
在\_EntryFormState
類別的最初建立時,設了一個property: _formKey
,想驗證前面的FormField
是否輸入符合要求,便可以使用_formKey.currentState
來判斷它的true/false。
那麼這個布林值又是怎麼設定的呢?這其實由FormState
的一個函式:validate()
來自動調整的。validate()
將會去跑Form
裡物件的validator()
,如上方在name所建立的。若有任何一個validator()
回傳為false,validate()
將會設_formKey.currentState
為false,反之,若validator()
全為true,validate()
會將_formKey.currentState
設為true。
當validate()
為true時,代表使用者輸入的內容符合要求,則將新資料新增至資料庫。最後,再將現在所在的頁面EntryForm從Navigator中pop出來,這部份的程式就完成啦!
final _formKey = GlobalKey<FormState>();
floatingActionButton: FloatingActionButton(
onPressed: () {
if (db != null && _formKey.currentState!.validate()) {
db?.insert(TimeBlock.detail(
name: controllers[0].text,
category: controllers[1].text,
notes: controllers[2].text));
Navigator.pop(context);
}
},
child: const Icon(Icons.add),
),
今天的內容就到這裡結束了,謝謝閱讀到這裡的讀者。
有任何問題或想說的都歡迎留言及email,明天會繼續努力的!